package org.ccay.security.authc.credential;

import java.util.Set;
import java.util.concurrent.Executors;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.ThreadFactory;
import java.util.concurrent.TimeUnit;

import org.apache.shiro.authc.AuthenticationInfo;
import org.apache.shiro.authc.AuthenticationToken;
import org.apache.shiro.authc.credential.HashedCredentialsMatcher;
import org.apache.shiro.cache.Cache;
import org.apache.shiro.cache.CacheManager;
import org.ccay.core.exception.ApplicationException;
import org.ccay.core.server.IBeforeShutdownHandler;
import org.ccay.core.server.ShutdownEventArgs;
import org.ccay.registry.util.RegistryQueryUtil;
import org.ccay.security.authc.UserInfoPasswordToken;
import org.ccay.security.entity.User;
import org.ccay.security.exception.ExcessiveAttemptsException;
import org.ccay.security.exception.IncorrectCredentialsException;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

public class RetryLimitHashedCredentialsMatcher extends HashedCredentialsMatcher implements Runnable,IBeforeShutdownHandler {
	
	private static final Logger log = LoggerFactory.getLogger(RetryLimitHashedCredentialsMatcher.class);

	private static String PASSWORDRETRYCACHE_KEY = "passwordRetryCache";
	
	/**
	 * 重试解锁线程
	 */
	private ScheduledExecutorService service;
	
	/**
	 * 重试次数
	 */
	private static String RETRY_LIMIT_KEY = "Ccay.Security.RetryLimit";
	
	/**
	 * 重试失败后锁定时间
	 */
	private static String LOCK_DURATION_KEY = "Ccay.Security.RetryLockDuration";
	
    private Cache<String, RetryLimitVO> passwordRetryCache;
    
    /**
     * 缓存管理器
     */
    private CacheManager cacheManager;
    
	public CacheManager getCacheManager() {
		return cacheManager;
	}
	public void setCacheManager(CacheManager cacheManager) {
		this.cacheManager = cacheManager;
	}
	
    @Override
    public boolean doCredentialsMatch(AuthenticationToken token, AuthenticationInfo info) {
    	int retryLimit = RegistryQueryUtil.getIntegerValue(RETRY_LIMIT_KEY);
    	if(null == cacheManager || retryLimit == 0){
    		return doSupperCredentialsMatch(null,token, info);
    	}
    	startCleanScheduled();
        String username = null;
        if(token instanceof UserInfoPasswordToken){
        	username = ((User)token.getPrincipal()).getUserName();
        }else{
        	username = token.getPrincipal().toString();
        }
        //retry count + 1
        RetryLimitVO retryCount = passwordRetryCache.get(username);
        if(retryCount == null) {
            retryCount = new RetryLimitVO();
            passwordRetryCache.put(username, retryCount);
        }
        if(retryCount.incrementAndGet() >= retryLimit) {
			throw new ExcessiveAttemptsException(
					(RegistryQueryUtil.getIntegerValue(LOCK_DURATION_KEY) * 1000
							- (int) (System.currentTimeMillis() - retryCount.getLastTryTime().getTime())) / 1000);
        }
        retryCount.touch();
        boolean matches = doSupperCredentialsMatch(retryCount,token, info);
        if(matches) {
            //clear retry count
            passwordRetryCache.remove(username);
        }
        return matches;
    }
    
    
	private boolean doSupperCredentialsMatch(RetryLimitVO limit, AuthenticationToken token, AuthenticationInfo info) {
		boolean matches = super.doCredentialsMatch(token, info);
		if (!matches) {
			String msg = "Submitted credentials for token [" + token + "] did not match the expected credentials.";
			if (null == limit) {
				throw new IncorrectCredentialsException(msg);
			} else {
				throw new IncorrectCredentialsException(msg,
						RegistryQueryUtil.getIntegerValue(RETRY_LIMIT_KEY) - limit.getRetryCount(),
						RegistryQueryUtil.getIntegerValue(LOCK_DURATION_KEY));
			}
		}
		return matches;
	}
    
	
	protected void startCleanScheduled(){
		// Scheduled has been started!
		if(null != service){
			return ;
		}
		//no cacheManager find!
		if(null == cacheManager){
			log.warn("login retry limit not enable!");
			return ;
		}
		//not need limit!
		if(RegistryQueryUtil.getIntegerValue(RETRY_LIMIT_KEY) <= 0){
			log.warn("login retry limit not enable!");
			return ;
		}
		//start schedule
		passwordRetryCache = cacheManager.getCache(PASSWORDRETRYCACHE_KEY);
		this.service = Executors.newSingleThreadScheduledExecutor(new ThreadFactory() {
			@Override
			public Thread newThread(Runnable r) {
				Thread thread = new Thread(r);  
	            thread.setDaemon(true);  
	            return thread;  
			}
		});
		this.service.scheduleAtFixedRate(this, 1, 1, TimeUnit.SECONDS);
		if (log.isDebugEnabled()) {
            log.debug("Retry limit clean scheduled started!");
        }
		
	}
    /**
     * 当用户重试锁定时间到期时解除锁定
     */
    protected int relieve(){
    	Set<String> keys = passwordRetryCache.keys();
    	int relieveCount = 0;
    	for(String key:keys){
    		RetryLimitVO rlv = passwordRetryCache.get(key);
    		if((System.currentTimeMillis() - rlv.getLastTryTime().getTime()) > RegistryQueryUtil.getIntegerValue(LOCK_DURATION_KEY) * 1000){
    			if (log.isDebugEnabled()) {
    	            log.debug("relieve "+key);
    	        }
    			passwordRetryCache.remove(key);
    			relieveCount++;
    		}
    	}
    	return relieveCount;
    }

	@Override
	public void run() {
        this.relieve();
	}

	@Override
	public int getOrder() {
		return 0;
	}

	@Override
	public void execute(Object sender, ShutdownEventArgs args) throws ApplicationException {
		if(null != service){
			service.shutdown();
			if (log.isDebugEnabled()) {
	            log.debug("Retry limit clean scheduled shutdown!");
	        }
		}
	}


}
